package ch.subgate.pongwars;

import android.bluetooth.BluetoothSocket;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * StreamThread is a separate thread from the basic game loop and the android UI thread. Basically
 * this is where all the bluetooth communication for multi-player matches happens. There are
 * basically two modes StreamThread can operate in. The first is the server mode: When in server mode
 * An iteration follows the following steps:
 * 1. send local player and ball positions
 * 2. receive the other player's x position and handle it with setDICX()
 *
 * It is important to note that the device operating in client mode does not execute any update()
 * calls. All simulation is done on the server. The client therefore follows the following steps:
 * 1. send local players x position
 * 2. receive the game state
 */
public class StreamThread extends Thread {

    /** The bluetooth socket used for receiving / sending data */
    private BluetoothSocket mSocket;

    /** The outgoing data stream */
    private ObjectOutputStream mOut;

    /** The incoming data stream */
    private ObjectInputStream mIn;

    private boolean mIsRunning;

    private GameContext mContext;

    private HumanPlayer mHumanPlayer;

    private NetworkPlayer mNetworkPlayer;

    private boolean mIsServer;

    public StreamThread(BluetoothSocket socket, GameContext gameContext, boolean isServer) {
        mSocket = socket;
        mContext = gameContext;
        mIsRunning = true;
        mIsServer = isServer;
        if (gameContext.playerRed instanceof NetworkPlayer) {
            mHumanPlayer = (HumanPlayer)gameContext.playerBlue;
            mNetworkPlayer = (NetworkPlayer)gameContext.playerRed;
        } else {
            mHumanPlayer = (HumanPlayer)gameContext.playerRed;
            mNetworkPlayer = (NetworkPlayer)gameContext.playerBlue;
        } // if
        try {
            mOut = new ObjectOutputStream(mSocket.getOutputStream());
            mOut.flush();
            mIn = new ObjectInputStream(mSocket.getInputStream());
        } catch (Exception e) { /* do nothing */ }
    } // Constructor

    public void setIsRunning(boolean isRunning) { mIsRunning = isRunning; }

    @Override
    public void run() {
        while (mIsRunning) {
            try {
                if (mIsServer) {
                    serverSend();
                    serverReceive();
                } else {
                    clientSend();
                    clientReceive();
                } // if
            } catch (Exception e) { /* do nothing */ }
        } // while

        try {
            mIn.close();
            mOut.close();
            mSocket.close();
        } catch (IOException e) { /* do nothing */ }
    } // run

    /**
     * Receives data from the bluetooth socket as a client and updates the NetworkPlayer and Ball
     * objects accordingly.
     * @throws IOException
     */
    public void clientReceive() throws  IOException {
        // update player data
        mNetworkPlayer.mX = mIn.readFloat();
        mNetworkPlayer.mSize = mIn.readFloat();
        mNetworkPlayer.mPoints = mIn.readInt();
        mHumanPlayer.mPoints = mIn.readInt();
        // update ball data
        mContext.ball.mX = mIn.readFloat();
        mContext.ball.mY = mIn.readFloat();
        // update timeout
        mContext.timeOut = mIn.readFloat();
    } // receiveMessage

    /**
     * Receives data from the bluetooth socket as a client and updates the NetworkPlayer and Ball
     * objects accordingly.
     * @throws IOException
     */
    public void serverReceive() throws IOException {
        // update player data
        mNetworkPlayer.setX(mIn.readFloat());
        mNetworkPlayer.setIsCharging(mIn.readBoolean());
        mNetworkPlayer.mSize = mIn.readFloat();
    } // serverReceive

    /**
     * Sends data about the local player in the following order:
     * 1. player x position (float)
     * 2. player ready state (boolean)
     * 3. player charging state (boolean)
     * 4. player size (float)
     * @throws IOException
     */
    public void clientSend() throws IOException {
        // send player data
        mOut.writeFloat(mHumanPlayer.mX);
        mOut.writeBoolean(mHumanPlayer.mIsCharging);
        mOut.writeFloat(mHumanPlayer.mSize);
        // flush the stream
        mOut.flush();
    } // sendMessage

    /**
     * Sends data about the local player and the ball in the following order:
     * 1. player x position (float)
     * 2. player size (float)
     * 3. player points (int)
     * 4. other player points (int)
     * 5. ball x position (float)
     * 6. ball y position (float)
     * 7. timeout in seconds (float)
     * @throws IOException
     */
    public void serverSend() throws IOException {
        // send player data
        mOut.writeFloat(mHumanPlayer.mX);
        mOut.writeFloat(mHumanPlayer.mSize);
        mOut.writeInt(mHumanPlayer.mPoints);
        mOut.writeInt(mNetworkPlayer.mPoints);
        // send ball data
        mOut.writeFloat(mContext.ball.mX);
        mOut.writeFloat(mContext.ball.mY);
        // send timeout
        mOut.writeFloat(mContext.timeOut);
        // flush the stream
        mOut.flush();
    } // sendMessage

} // StreamThread
